﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PercolationTree.Model;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;

namespace PercolationTree.Drawing
{
    internal struct DrawableNode
    {
        public Point point;
        public int radius;
        public Color color;
        public Node node;
        public double deltaFi;
    }

    internal class DrawableNodeComparer : IEqualityComparer<DrawableNode>
    {
        public bool Equals(DrawableNode x, DrawableNode y)
        {
            return (x.point.X == y.point.X && x.point.Y == y.point.Y);
        }

        public int GetHashCode(DrawableNode obj)
        {
            return obj.GetHashCode();
        }
    }

    class DrawableTree : IDrawable
    {
        private Tree tree;

        public DrawableTree(Tree tree)
        {
            this.tree = tree;
        }

        public void draw(PaintEventArgs e)
        {
            BufferedGraphics buffer = initBuffer(e);

            HashSet<DrawableNode> drawableNodes = new HashSet<DrawableNode>();

            int x0 = e.ClipRectangle.Width / 2;
            int y0 = e.ClipRectangle.Height / 2;

            int numberOfLayers = tree.Layers.Count;
            int r = (int) (0.05 * Math.Min(x0, y0));
            int deltaLayer = Math.Min(x0, y0);
            deltaLayer = (int) (deltaLayer / ((double)numberOfLayers - 0.5));
            int layerR = 0;
            double deltaR = 1;
            double areaFix = Math.PI / Math.Sqrt(2.3);
            DrawableNodeComparer comparer = new DrawableNodeComparer();
            Dictionary<Node, DrawableNode> allNodes = new Dictionary<Node, DrawableNode>();

            Random random = new Random();

            for (int i = 0; i < numberOfLayers; i++)
            {
                Layer layer = tree.Layers[i];
                double fi = 0;
//                if (layer.Nodes[0].Supernode != null)
//                   fi = allNodes[layer.Nodes[0].Supernode].deltaFi / 2 / layer.Nodes[0].Supernode.Subnodes.Count;
                int j;
                for (j = 0; j < layer.Nodes.Count; j++)
                {
                    Node node = layer.Nodes[j];
                    if (node.Supernode != null)
                        fi += allNodes[node.Supernode].deltaFi / 2 / node.Supernode.Subnodes.Count;
                    int x = (int)(x0 + layerR * Math.Cos(fi));
                    int y = (int)(y0 + layerR * Math.Sin(fi));
                    if (node.Supernode != null)
                        fi -= allNodes[node.Supernode].deltaFi / 2 / node.Supernode.Subnodes.Count;
                    Point point = new Point(x, y);
                    DrawableNode dNode = new DrawableNode();
                    dNode.node = node;
                    dNode.point = point;
                    dNode.radius = r;
                    if (node.Supernode == null)
                        dNode.deltaFi = 2 * Math.PI;
                    else
                        dNode.deltaFi = allNodes[node.Supernode].deltaFi / node.Supernode.Subnodes.Count;

                    if (node.Type == NodeType.Valid)
                        dNode.color = Color.Green;
                    else if (node.Supernode.Type == NodeType.Valid && node.Type == NodeType.Invalid)
                        dNode.color = NextClasterColor();
                    else if (node.Supernode.Type == NodeType.Invalid)
                        dNode.color = allNodes[node.Supernode].color;
                    //if (!drawableNodes.Contains(dNode, comparer))
                    {
                        drawableNodes.Add(dNode);
                        using (Pen coloredPen = new Pen(dNode.color))
                        {
                            if (node.Supernode != null && node.Type == node.Supernode.Type)
                                buffer.Graphics.DrawLine(coloredPen, point, allNodes[node.Supernode].point);
                        }
                    }
                    allNodes.Add(node, dNode);
                    fi += dNode.deltaFi;
                }
                if (i > 0 && tree.Layers[i - 1].Nodes.Count < layer.Nodes.Count)
                    deltaR = (double)tree.Layers[i - 1].Nodes.Count / (double)layer.Nodes.Count * areaFix;
                if (deltaR > 0.99)
                    deltaR = 0.87;
                r = Math.Max((int)(r * deltaR), 1);
                layerR += deltaLayer;
            }
            drawCircles(drawableNodes, buffer.Graphics);
            buffer.Render(e.Graphics);
            buffer.Dispose();
        }

        private BufferedGraphics initBuffer(PaintEventArgs e)
        {
            BufferedGraphicsContext currentContext;
            BufferedGraphics buffer;
            currentContext = BufferedGraphicsManager.Current;
            buffer = currentContext.Allocate(e.Graphics, e.ClipRectangle);
            buffer.Graphics.Clear(Color.White);
            return buffer;
        }

        private static int colorIndex = 0;
        private Color NextClasterColor()
        {
            Color[] colors = { Color.Red, Color.Blue, Color.Magenta, Color.Aquamarine, Color.Brown, Color.DarkCyan };
            colorIndex %= colors.Length;
            return colors[colorIndex++];
        }

        private void drawCircles(HashSet<DrawableNode> nodes, Graphics g)
        {
            Pen pen = new Pen(Color.Black);

            foreach (DrawableNode dnode in nodes)
            {
                Point point = dnode.point;
                Color color = dnode.color;
                using (Brush brush = new SolidBrush(color))
                {
                    int r = dnode.radius;
                    if (r > 1)
                    {
                        g.FillEllipse(brush, point.X - r, point.Y - r, 2 * r, 2 * r);
                        g.DrawEllipse(pen, point.X - r, point.Y - r, 2 * r, 2 * r);
                    }
                    else
                        g.FillRectangle(brush, point.X, point.Y, 1, 1);
                }
            }
            pen.Dispose();
        }
    }
}
